#  apk加固工具
# apktool.jar进行解包，替换lib目录下的so文件
# AndResGuard.jar 进行资源混淆
# 并打印出apk中resources.arsc文件和r目录下所有文件的crc签名
#  1、资源压缩、混淆
#  2、动态向cpp代码中加入apk中文件的CRC签名，C++编译，生成so，并替换或者添加到apk中。
#  3、v1+v2+v3签名
#
#Android11 进行资源压缩的apk必须进行4字节对齐处理，zipalign -v -p 4
import os
import shutil
import tools
import FileTools

apkPath_teacher = "/Users/xuyan/Desktop/eng100-teacher-apk/"
# apkPath_student = "/Users/xuyan/Desktop/testApk/"
apkPath_student = "/Users/xuyan/Desktop/eng100-student-apk/"

dirPath = "./"
#工具目录
bin = dirPath + "bin/"
student_sign = bin+"eng100.jks"
teacher_sign = bin+"eng100_teacher.jks"
#解压与压缩apk工具
apktool = bin+"apktool_2.5.0.jar"
#动态库替换目录
lib_so = bin + "so_lib/"
# NDK目录
ndk_path = "/Users/xuyan/Library/Android/sdk/ndk/21.3.6528147/ndk-build"
#安全CPP代码的根路径
ndk_cpp_path = bin+"cpp/"
ndk_cpp_libs = ndk_cpp_path+"libs/"
ndk_cpp_code_path = ndk_cpp_path+"appSecurity.cpp"
#资源压缩工具
and_res_guard = bin+"AndResGuard.jar"
stu_res_config = bin +"config-student.xml"
teacher_res_config = bin +"config-teacher.xml"
#apk签名
apksigner = bin+"apksigner"
#zip压缩工具
zip7za_path = bin+"p7zip/16.02_2/bin/7za"
#资源对齐工具
zipalign_path = bin+"zipalign"
#学生端签名文件
eng100Jks = bin+"eng100.jks"
#教师端签名文件
eng100TeacherJks = bin+"eng100_teacher.jks"
#工作目录
work_path = bin+"work/"
work_path_res = work_path+"resource/"#压缩资源目录
workType = 0 #学生端：0，教师端：1
#是否生成加固组件
isGenerateSecurity = False

#解压apk回调
def unZipApkCallbackFunc(type,target,delDir):
    if type == 0:
        print("解压成功")
        apkDirPath = target.replace(".apk","")
        path = apkDirPath + "/lib/"
        toPath = dirPath+apkDirPath[apkDirPath.rfind("/")+1:len(apkDirPath)]
        if not isGenerateSecurity:
            replaceLibSo(toPath, toPath)
        else:
            zipApk(toPath, toPath)
    else:
        print("解压失败")


# 解压APK
def unzipApk(apk):
    command_line = "java -jar "+apktool+" d "+apk
    tools.executeCommand(apk,command_line,unZipApkCallbackFunc,"")


def onZipApkCallbackFunc(type,target,delDir):
    if type == 0:
        print("压缩成功")
        index = target.rfind("/")
        apkPath = target[index+1:len(target)]+".apk"
        path = target + "/dist/"+apkPath
        print("压缩资源读取地址："+path)
        confusionResource(path,delDir)
    else:
        print("压缩失败")

# apk打包
def zipApk(path,delDir):
    command_line = "java -jar " + apktool + " b " + path
    tools.executeCommand(path, command_line, onZipApkCallbackFunc,delDir)

#apk资源压缩成功
def onConfusionResourceCallbackFunc(type,target,delDir):
    if FileTools.dirIsExists(delDir):
        FileTools.delFile(delDir)
    if type == 0:
        print("资源压缩成功")
        dir = FileTools.readDir(work_path_res)
        apk = target[target.rfind("/")+1:len(target)-len(".apk")]
        for r in dir:#eng100-student-1.2.10-Wanhe-release_signed_7zip_aligned-signed.apk
            if r.startswith(apk+"_signed_aligned"):
                apkPath = work_path_res+r
                signedApk(apkPath)
    else:
        print("资源压缩失败")
    if FileTools.dirIsExists(work_path_res):
        FileTools.delFile(work_path_res)


# 混淆资源文件
# java -jar andresguard.jar -repackage input.apk -out output_directory
#  -7zip /usr/local/bin/7za -zipalign /Users/xuyan/Library/Android/sdk/build-tools/29.0.3/zipalign
def confusionResource(apk,delDir):
    res_config = stu_res_config
    if not FileTools.dirIsExists(work_path_res):
        FileTools.mkDir(work_path_res)
    if workType == 1:
        res_config = teacher_res_config
    command_line = "java -jar " + and_res_guard \
                   + " "+apk+ " -config " + res_config + " -out " + work_path_res\
                   + " -7zip "+zip7za_path+" -zipalign "+zipalign_path

    print("压缩资源的命令："+command_line)
    tools.executeCommand(apk, command_line, onConfusionResourceCallbackFunc,delDir)

def onSignedCallbackFunc(type,target,delDir):
    if FileTools.dirIsExists(delDir):
        FileTools.delFile(delDir)
    if type == 0:
        print(target+"签名成功")
        # 计算apk的CRC签名
        if isGenerateSecurity:
            canculateApkRCR(target)
    else:
        print(target+"签名失败")

#
# zipAlign对so进行4字节对齐
#
def zipAlignApk(apk,outApk):
    command_line = zipalign_path+" -v -f 4 "+apk+" "+outApk
    tools.executeCommand(apk, command_line, onZipAlignApkCallbackFunc, outApk)

def onZipAlignApkCallbackFunc(type,target,out):
    if type == 0:
        print("字节对齐成功")
        signedApk(out)
    else:
        print(target + "字节对齐失败")

# /Users/xuyan/Library/Android/sdk/build-tools/29.0.3/apksigner  sign --ks eng100_teacher.jks --ks-key-alias eng100 --out eng100-teacher-tencent.apk eng100-teacher-1.0.4-Tencent-release_signed.apk
# v1+v2+v3签名
def signedApk(apk):
    signJks = eng100Jks
    path = "student/"
    if workType == 1:
        signJks = eng100TeacherJks
        path = "teacher/"
    apkName = apk[apk.rfind("/")+1:(len(apk)-len(".apk"))]
    out_apk = work_path + path
    if not FileTools.dirIsExists(out_apk):
        FileTools.mkDir(out_apk)
    out_apk = out_apk + apkName+"-signed.apk"
    command_line = apksigner+" sign --ks " + signJks  \
                   + " --ks-key-alias eng100 --ks-pass pass:wanhe100 --key-pass pass:wanhe100 --out " + out_apk +" "+apk
    print(command_line)
    tools.executeCommand(out_apk, command_line, onSignedCallbackFunc,apk)

#计算CRC签名，并修改appSecurity.cpp,然后生成so库
def canculateApkRCR(apk):
    rPathCRC = FileTools.canculateCRC(apk, "r/")
    resourcesCRC = FileTools.canculateCRC(apk, "resources.arsc")
    dexCRC = FileTools.canculateCRC(apk, ".dex")
    #动态更改appSecurity.cpp文件中的apkResourceCRC、rFileCRC、dexCRC值
    file = ndk_cpp_code_path
    output = []
    with open(file, "+r", encoding="utf-8")as f:
        for line in f.readlines():
            if "*apkResourceCRC" in line:
                print(line)
                line = os.path.join('const char *apkResourceCRC = "',resourcesCRC,'";\n')
                line = line.replace("/", "")
            elif "*rFileCRC" in line:
                print(line)
                line = os.path.join('const char *rFileCRC = "' , rPathCRC , '";\n')
                line = line.replace("/", "")
            elif "*dexCRC" in line:
                print(line)
                line = os.path.join('const char *dexCRC = "' , dexCRC , '";\n')
                line = line.replace("/","")
            output.append(line)
        f.close()
    with open(file, "w", encoding="utf-8")as wf:
        wf.writelines(output)
        wf.close()
    compileSo(apk)

# 替换so文件
def replaceLibSo(path,delDir):
    fileList = FileTools.readDir(ndk_cpp_libs)
    print("替换so文件：")
    for f in fileList:
        lib_path = ndk_cpp_libs +f+"/"
        for f_dir in FileTools.readDir(lib_path):
            os_path = lib_path + f_dir
            lib_os_path = path+"/lib/" + f + "/" + f_dir
            if FileTools.dirIsExists(lib_os_path):#只拷贝apk中存在的so
                print("%s ===> %s" % (os_path, path + f))
                shutil.copyfile(os_path,lib_os_path)
    apkPath = path.replace("/lib/","")
    zipApk(delDir,delDir)

# cpp_so_lib = ndk_cpp_libs
def onCompileSoCallbackFunc(type,target,delDir):
    if type == 0:
        print("so生成成功")
        FileTools.delFile(target)
    else:
        print("so生成失败")

# 编译so文件
def compileSo(apk):
    command_line = ndk_path+" NDK_PROJECT_PATH="+bin+"cpp APP_BUILD_SCRIPT="+bin+"cpp/Android.mk NDK_APPLICATION_MK="+bin+"cpp/Application.mk"
    print(command_line)
    tools.executeCommand(apk,command_line,onCompileSoCallbackFunc,apk)

if __name__ == '__main__':
    apkPath = apkPath_student
    if workType == 1:
        apkPath = apkPath_teacher
    apkList = FileTools.readFile(apkPath)
    #先移除加固组件
    FileTools.del_dir_in_files(ndk_cpp_libs)
    #加固组件生成
    isGenerateSecurity = True
    sectionApk = apkList[0]
    unzipApk(sectionApk)
    for apk in apkList:
        print(apk)
        isGenerateSecurity = False
        unzipApk(apk)
    # canculateApkRCR("./bin/work/student/eng100-student-1.2.14-_360-release_signed_aligned-signed.apk")


